#include "Application.h"

#include <algorithm>
#include <sstream>
#include <windowsx.h>

#include "main/Globals.h"
#include "main/WindowManager.h"

#include "managers/SettingsManager.h"

#include "scenes/Scene.h"

#include "utils/Camera.h"
#include "utils/DxErrorChecker.h"
#include "utils/MathUtils.h"

Application::Application(Scene* const scene) 
: mScene(scene)
{
	assert(scene != nullptr);
	mScene->init();
}

Application::~Application() {
	assert(mScene != nullptr);

	delete mScene;
}

void 
Application::execute() {
	mTimer.reset();
	double elapsedTime = 0.0;
	const double msPerFrame = Globals::gSettingsManager->mMsPerFrameDouble;

	
	bool done = false;
	while (!done) {
		// Tick timer to update delta time.
		// Once elapsed time reaches MS_PER_FRAME (based on desired FPS)
		// we will execute a frame.
		mTimer.tick();
		elapsedTime += mTimer.deltaTime();
		if (elapsedTime < msPerFrame) {
			continue;
		} else {
			elapsedTime -= msPerFrame;
		}

		done = processMessages();
		
		// Update and draw scene
		calculateFrameStats();
		updateScene();
		mScene->draw();
	}
}

bool 
Application::processMessages() {
	MSG msg;

	// Process messages until close window request.
	while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);

		if (msg.message == WM_QUIT) {
			return true;
		}
	}

	return false;
}

void 
Application::updateScene() {
	//
	// Control the camera.
	//
	const float factor = 50.0f;
	const float msPerFrame = Globals::gSettingsManager->mMsPerFrameFloat;
	const float distance = factor * msPerFrame;

	// Move forward
	if (GetAsyncKeyState('W') & 0x8000) {
		Globals::gCamera.walk(distance);
	}

	// Move backward
	if (GetAsyncKeyState('S') & 0x8000) {
		Globals::gCamera.walk(-1.0f * distance);
	}

	// Strafe left
	if (GetAsyncKeyState('A') & 0x8000) {
		Globals::gCamera.strafe(-1.0f * distance);
	}

	// Strafe right
	if (GetAsyncKeyState('D') & 0x8000) {
		Globals::gCamera.strafe(distance);
	}
	
	// Go through Y axis
	if (GetAsyncKeyState('G') & 0x8000) {
		Globals::gCamera.worldPosition().y -= 5.0f;
	}
	else if (GetAsyncKeyState('H') & 0x8000) {
		Globals::gCamera.worldPosition().y += 5.0f;
	}

	// Go through Z axis
	if (GetAsyncKeyState('B') & 0x8000) {
		Globals::gCamera.worldPosition().z -= 5.0f;
	}
	else if (GetAsyncKeyState('N') & 0x8000) {
		Globals::gCamera.worldPosition().z += 5.0f;
	}

	// Go through X axis
	if (GetAsyncKeyState('T') & 0x8000) {
		Globals::gCamera.worldPosition().x -= 5.0f;
	}
	else if (GetAsyncKeyState('Y') & 0x8000) {
		Globals::gCamera.worldPosition().x += 5.0f;
	}

	// Execute compute shader or not
	if (GetAsyncKeyState('P') & 0x8000) {
		Globals::gExecCS = false;
	}
	else if (GetAsyncKeyState('O') & 0x8000) {
		Globals::gExecCS = true;
	}
}

void 
Application::calculateFrameStats() {
	static uint32_t sFrameCounter = 0;
	static float sTimeElapsed = 0.0f;

	++sFrameCounter;

	// Compute averages over one second period.
	if ((mTimer.executionTime() - sTimeElapsed) >= 1.0f) {
		// fps = frameCnt / 1
		const float framesPerSecond = static_cast<float> (sFrameCounter);
		const float millisecondsPerFrame = 1000.0f / framesPerSecond;

#if defined(DEBUG) | defined(_DEBUG)
		const std::wstring windowTitle = L"Debug";
#else
		const std::wstring windowTitle = L"Release";
#endif

		std::wostringstream outs;
		outs.precision(6);
		outs << windowTitle.c_str() << L"    " << L"FPS: " << framesPerSecond << L"    " << L"Frame Time: " << millisecondsPerFrame << L" (ms)";

		assert(Globals::gWindowData);
		SetWindowText(Globals::gWindowData->mHandler, outs.str().c_str());

		// Reset for next average.
		sFrameCounter = 0;
		sTimeElapsed += 1.0f;
	}
}